import sys
import wx
import os
import re

from shutil import copy2, copy
from urlparse import urlsplit, urlunsplit
from urllib import quote, unquote
from hashlib import sha1

from BitTornado.bencode import bencode

from LMG.Torrent.torrent import Torrent, MAGNET_PREFIX

from LMG.Utility.constants import * # pylint: disable-msg = W0611,W0401
from LMG.Utility.helpers import get_metainfo, findUniqueFileName
from LMG.Utility.urlfinder import HtmlTorrentScanner


def isSameTorrent(torrent1src, torrent2src):
    """
    Compare two torrents to see if they are the same
    """
    
    # Same location
    if torrent1src == torrent2src:
        return True

    metainfo1 = get_metainfo(torrent1src)
    if metainfo1 is None:
        return False
    
    metainfo2 = get_metainfo(torrent2src)
    if metainfo2 is None:
        return False
    
    metainfo_hash1 = sha1(bencode(metainfo1)).hexdigest()
    metainfo_hash2 = sha1(bencode(metainfo2)).hexdigest()
    
    # Hash values for both torrents are the same
    if metainfo_hash1 == metainfo_hash2:
        return True
        
    return False


################################################################

class AddTorrents:
    """
    Deal with adding torrents to the list
    """
    def __init__(self):
        pass

    def AddTorrentURL(self, url, dest = None, caller="", cookies = None, label = None):
        # Strip any leading/trailing spaces from the URL
        url = url.strip()
        
        url = url.encode("unicode_escape")
        
        # Copy file from web and call addnewproc
        #########################################
        btmetainfo = get_metainfo(url, style="url", cookies = cookies)
        if btmetainfo is None:
            errormsg = _("Can't get torrent from this URL") + ":\n" + url
            if caller not in ["web", "command"]:
                #display error can't connect to server
                dialog = wx.MessageDialog(None,
                                          errormsg,
                                          _('Error'), 
                                          wx.ICON_ERROR)
                dialog.ShowModal()
                dialog.Destroy()
            return False, errormsg, None

        # Backup metainfo from URL to local directory
        url_splitted = urlsplit(url)
        filename = os.path.split(''.join([unquote(url_splitted[2]), url_splitted[3]]))[1]
        
        return self.AddTorrentFromMetainfo(btmetainfo, filename = filename, dest = dest, caller = caller, label = label)
        
    def AddTorrentFromBencodeData(self, data, dest = None, caller="", label = None):
        btmetainfo = get_metainfo(data, style="rawdata")
        if btmetainfo is None:
            errormsg = _('Failed : Invalid torrent file')
            
            if caller not in ["web", "command"]:
                #display error can't connect to server
                dialog = wx.MessageDialog(None, 
                                          errormsg, 
                                          _('Error'), 
                                          wx.ICON_ERROR)
                dialog.ShowModal()
                dialog.Destroy()
            return False, errormsg, None
        
        return self.AddTorrentFromMetainfo(btmetainfo, dest = dest, caller = caller, label = label)
    
    def AddTorrentFromMetainfo(self, metainfo, filename = None, dest = None, caller="", label = None):
        """
        Given metainfo for a torrent, add it to the list
        """
        try:
            info_filename = metainfo["info"]["name"] + ".torrent"
        except KeyError:
            # Unable to get the "name" attribute from the .torrent
            # looks like the file is likely invalid
            
            # No name in the torrent file
            errormsg = _('Failed : Invalid torrent file') + ":" + \
                       "\n" + \
                       _('Torrent file contains no \'name\'')
            
            if caller not in ["web", "command"]:            
                dialog = wx.MessageDialog(None, 
                                          errormsg, 
                                          _('Error'), 
                                          wx.ICON_ERROR)
                dialog.ShowModal()
                dialog.Destroy()
            return False, errormsg, None
        
        if not filename:
            # Try getting the filename from the torrent
            filename = info_filename
            if type(filename) is not unicode:
                filename = unicode(filename, sys.getfilesystemencoding())
            
        torrentsrc = os.path.join(utility.getConfigPath(), "torrent", filename)

        fileexists = os.access(torrentsrc, os.R_OK)

        if not fileexists:
            f = open(torrentsrc, "wb")
            f.write(bencode(metainfo))
            f.close()
        
        # Torrent either already existed or should exist now
        dotTorrentDuplicate = True
        
        return self.AddTorrentFromFile(torrentsrc, False, dotTorrentDuplicate, dest = dest, caller = caller, label = label)        
    
    def AddTorrentLink(self, event = None):
        starturl = ""
        try:
            # See if there's a url in the clipboard
            # If there is, use that as the default for the dialog
            text = None
            if wx.TheClipboard.Open():
                data = wx.TextDataObject()
                gotdata = wx.TheClipboard.GetData(data)
                wx.TheClipboard.Close()
                if gotdata:
                    text = data.GetText()
            if text is not None:
#                if text.startswith("http://") and text.endswith(".torrent") or text.startswith(MAGNET_PREFIX):
                if text.startswith("http://") and text.endswith(".torrent"):
                    starturl = text
        except:
            pass
        
        dialog = wx.TextEntryDialog(None, 
                                    _('Enter the URL for the torrent you wish to add:'), 
                                    _('Add Torrent from URL'), 
                                    starturl)

        result = dialog.ShowModal()
        btlink = dialog.GetValue()
        dialog.Destroy()
        
        if result != wx.ID_OK:
            return
        
        if btlink.startswith(MAGNET_PREFIX):
            notyet = wx.MessageDialog(None, _("Magnet links are not supported (yet), sorry."), _("Error"), wx.OK)
            notyet.ShowModal()
            notyet.Destroy()
        elif btlink:
            self.AddTorrentURL(btlink)
            

    def AddTorrentNoneDefault(self, event = None):
        self.AddTorrentFile(event, True)

    def OnScanTorrentDir(self, event = None):
        dialog = wx.DirDialog(None, 
                              _('Choose a directory to scan'), 
                              utility.getLastDir("open"),
                              style = wx.DD_DEFAULT_STYLE)
        result = dialog.ShowModal()
        path = dialog.GetPath()
        dialog.Destroy()
        
        if result == wx.ID_OK:
            utility.lastdir["open"] = path
            self.ScanTorrentDir(path)
        
    def ScanTorrentDir(self, path):
        scanned = [entry for entry in os.listdir(path) if (os.path.isfile(os.path.join(path, entry)) and entry.endswith(".torrent"))]
        for torrent in scanned:
            src = os.path.join(path, torrent)
            self.AddTorrentFromFile(src)
            
    def OnScanURL(self, event = None):
        starturl = ""
        try:
            # See if there's a url in the clipboard
            # If there is, use that as the default for the dialog
            text = None
            if wx.TheClipboard.Open():
                data = wx.TextDataObject()
                gotdata = wx.TheClipboard.GetData(data)
                wx.TheClipboard.Close()
                if gotdata:
                    text = data.GetText()
            if text is not None:
                if text.startswith("http://") and text.endswith(".torrent"):
                    starturl = text
        except:
            pass
        
        dialog = wx.TextEntryDialog(None, 
                                    _('Enter the URL of the page you wish to scan:'), 
                                    _('Scan URL'), 
                                    starturl)

        result = dialog.ShowModal()
        url = dialog.GetValue()
        dialog.Destroy()
        
        if result == wx.ID_OK and url:
            self.ScanUrl(url)

    def ScanUrl(self, url, single = False, allowBrowser = False):
        HtmlTorrentScanner(self, url, single = single, allowBrowser = allowBrowser).start()
        
    def HtmlTorrentScannerFailed(self, article, message):
        wx.LogError(message + ' ' + article)
    
    def HtmlTorrentScannerSucceeded(self, article, rule, data):
        wx.LogMessage(_("Downloaded:") + ' ' + article)
        wx.CallAfter(self.AddTorrentFromBencodeData, data.read(), caller = "web")
            
    def AddTorrentFile(self, event = None, forceasklocation = False):
        dialog = wx.FileDialog(None, 
                               _('Choose a torrent file'), 
                               utility.getLastDir("open"), 
                               '', 
                               _('Torrent Files') + ' (*.torrent)|*.torrent|' + \
                               _('All Files') + ' (*.*)|*.*', 
                               wx.OPEN|wx.MULTIPLE)
        result = dialog.ShowModal()
        dialog.Destroy()
        if result != wx.ID_OK:
            return
        
        filelocation = dialog.GetPaths()

        for filepath in filelocation:
            self.AddTorrentFromFile(filepath, forceasklocation)
           
    def AddTorrentFromFile(self, filepath, forceasklocation = False, dotTorrentDuplicate = False, dest = None, caller = "", label = None):
        if type(filepath) is not unicode:
            filepath = unicode(filepath, sys.getfilesystemencoding())

        # Check to make sure that the source file exists
        sourcefileexists = os.access(filepath, os.R_OK)
        
        if not sourcefileexists:
            errormsg = filepath + \
                       '\n' + \
                       _('Failed : .torrent file does not exist or cannot be read.')
            
            if caller not in ["web", "command"]:
                dialog = wx.MessageDialog(None, 
                                          errormsg,
                                          _('Error'), 
                                          wx.OK|wx.ICON_ERROR)
                result = dialog.ShowModal()
                dialog.Destroy()

            # What do we do if the source file doesn't exist?
            # Just return if the source file doesn't exist?
            return False, errormsg, None

        # Make torrent directory if necessary
        utility.MakeDir("torrent")
      
        torrentpath = os.path.join(utility.getConfigPath(), "torrent")    
        filename     = os.path.split(filepath)[1]
        torrentsrc   = os.path.join(torrentpath, filename)
        dontremove = False
        fileexists = os.access(torrentsrc, os.R_OK)
        
        # If the two files are identical, just point to the
        # .torrent file in the /torrent directory
        sametorrent = isSameTorrent(filepath, torrentsrc)
        if sametorrent:
            filepath = torrentsrc
        else:
            newtorrentsrc = findUniqueFileName(torrentsrc, caller)
            if newtorrentsrc:
                torrentsrc = newtorrentsrc
                dotTorrentDuplicate = True

        # Is the torrent already present in the list?
        torrentinlist = self._checkForDuplicateInList(src = filepath)
        if torrentinlist:
            if caller == "drag":
                return False, "", None
            return self._dupFileInList(filepath, caller), None
        
        if fileexists and not dotTorrentDuplicate:
            errormsg = _('This torrent is a duplicate! \nAre you sure you want to replace it?')
            
            if caller in ["web", "command"]:
                return False, errormsg, None

            # ignore if the src and dest files are the same
            # this means that files in the torrent directory
            # will only give a duplicate torrent error if
            # they are already loaded in the list
            # (dotTorrentDuplicate stays False and the check to
            #  see if it's in the list is performed in _addNewProc)
            ##############################################
            if (filepath == torrentsrc):
                # If _addNewProc finds that the torrent is already in the proctab,
                # we don't want to remove it otherwise the torrent that is running
                # will be in trouble
                dontremove = True
            else:
                # There is a duplicate .torrent file in /torrent
                dialog = wx.MessageDialog(None, 
                                          errormsg,
                                          _('Duplicate torrent'), 
                                          wx.YES_NO|wx.ICON_EXCLAMATION)
                result = dialog.ShowModal()
                dialog.Destroy() 
                if(result == wx.ID_NO):
                    return False, errormsg, None
                else:
                    dotTorrentDuplicate = True
        else:
            # Either:
            # dotTorrentDuplicate was False and the file didn't exist (no change)
            # dotTorrentDuplicate was True before when coming from AddTorrentURL (still need to check the list)
            dotTorrentDuplicate = False

        # No need to copy if we're just copying the file onto itself
        if (filepath != torrentsrc):
            try:
                copy2(filepath, torrentsrc)
            except:
                copy(filepath, torrentsrc)
        return self._addNewProc(torrentsrc, 
                                dest = dest, 
                                forceasklocation = forceasklocation, 
                                dotTorrentDuplicate = dotTorrentDuplicate, 
                                dontremove = dontremove, 
                                caller = caller,
                                new = True,
                                label = label)

    #
    # Add a torrent to the list
    # Torrents can be added from 3 sources:
    #   from file
    #   from URL
    #   autoadd (command line)
    #
    def _addNewProc(self, src, dest = None, forceasklocation = False, dotTorrentDuplicate = False,
                    dontremove = False, caller = "", doupdate = True, new = False, label = None):
        #from file, URL maybe down torrent.lst from addProc
        # change at onChooseFile make sure they choose dest
        # dotTorrentDuplicate : To avoid asking the user twice about duplicate (for torrent file name and torrent name)
        #                       True if .torrent is duplicate ; not used if caller==web"

        # Did we succeed in adding the torrent?
        errormsg = None
        torrent = None
        
        # Check to see the the src file actually exists:
        if not src.startswith(MAGNET_PREFIX) and not os.access(src, os.R_OK):
            errormsg = src + '\n' + _('Torrent File is missing')
            
            if caller not in ["web", "command"]:
                dlg = wx.MessageDialog(None, 
                                       errormsg, 
                                       _('Error'), 
                                       wx.OK|wx.ICON_ERROR)
                result = dlg.ShowModal()
                dlg.Destroy()
                dontremove = True
        else:
            torrent = Torrent(src, dest = dest, forceasklocation = forceasklocation, caller = caller)       
            
            if torrent.metainfo is None:
                errormsg = src + '\n' + \
                           _('Failed : Invalid torrent file')
                
                if caller not in ["web", "command"]:
                    dlg = wx.MessageDialog(None, 
                                           errormsg + '\n' + \
                                           _('Do you want to remove this torrent?'), 
                                           _('Error'), 
                                           wx.YES_NO|wx.ICON_ERROR)
                    result = dlg.ShowModal()
                    dlg.Destroy()
                    if (result == wx.ID_NO):
                        dontremove = True
                    
            # If the torrent doesn't have anywhere to save to, return with an error
            elif torrent.files.dest is None:
                errormsg = _('No default destination and no destination specified to save to.')

            # Search for duplicate torrent name (inside .torrent file) and hash info
            # only if the .torrent is not already a duplicate
            elif not dotTorrentDuplicate:
                torrentInList = self._checkForDuplicateInList(torrent.infohash, torrent.src)
                if torrentInList:
                    dupsuccess, duperrormsg = self._dupFileInList(src, caller)
                    if not dupsuccess:
                        errormsg = duperrormsg
                        

        # We encountered an error somewhere in the process
        if errormsg is not None:
            # Don't remove if the torrent file is already being used by an existing process
            # Removing will cause problems with the other process
            if not dontremove:
                try:
                    os.remove(src)
                except:
                    pass
            torrent = None
            wx.LogError(errormsg)
            return False, errormsg, torrent
       
        if doupdate and torrent is not None:
            torrent.postInitTasks()
            
            # Update torrent.list
            torrent.torrentconfig.writeSrc(False)
            utility.torrentconfig.Flush()
            
            utility.queue.updateAndInvoke()

            # Torrent information for trigger rules
            torrentname = torrent.getTitle(kind = "original")
            torrentfilename = torrent.getTitle(kind = "torrent")
            trackerlist = []
            for tier in torrent.trackerlist:
                trackerlist += tier
            fileslist = []
            if torrent.files.isFile():
                fileslist.append(torrentname.lower())
            else:
                for f in torrent.info['files']:
                    if int(f['length']) > 0:
                        received = ''
                        for item in f['path']:
                            received = os.path.join(received, item)
                        fileslist.append(received.lower())
            # Search for trigger rules match
            templates = list(utility.labelmanager.l_templates)
            for t in templates:
                match = None
                template = utility.labelmanager.get(t)
                if template.isTriggerActive():
                    # Match trackers
                    if template.triggermatch["Tracker"]:
                        for tracker in trackerlist:
                            match = re.search(template.triggermatch["Tracker"], tracker, re.IGNORECASE)
                            if match: break
                    # Match torrent
                    if not match and template.triggermatch["Torrent"]:
                        match = re.search(template.triggermatch["Torrent"], torrentname, re.IGNORECASE)
                    # Match Torrent File
                    if not match and template.triggermatch["TorrentFile"]:
                        match = re.search(template.triggermatch["TorrentFile"], torrentfilename, re.IGNORECASE)
                    # Match files
                    if not match and template.triggermatch["File"]:
                        for filename in fileslist:
                            match = re.search(template.triggermatch["File"], filename, re.IGNORECASE)
                            if match: break
                    # Apply Label
                    if match:
                        torrent.connection.applyLabel(t)
            if label:
                torrent.connection.applyLabel(label)
        
        return True, _('OK'), torrent

    #
    # Add a torrent that's already been loaded into the list
    #
    def _addOldProc(self, src):       
        # Torrent information
        if not src.startswith(MAGNET_PREFIX):
            filename = os.path.join(utility.getConfigPath(), "torrent", src)
        else:
            filename = src
        success, error, torrent = self._addNewProc(filename, dest = None, doupdate = False)
        
        if not success:
            # Didn't get a valid Torrent object
            return False
        
        torrent.postInitTasks()
        
        return True
             
    #
    # Load torrents from the torrent.list file
    #
    def readTorrentList(self):        
        numbackups = 3
        
        # Manage backups
        filenames = [ os.path.join(utility.getConfigPath(), "torrent.list") ]
        for i in range(1, numbackups + 1):
            filenames.append(filenames[0] + ".backup" + str(i))
            
        for i in range (numbackups, 0, -1):
            if os.access(filenames[i-1], os.R_OK):
                try:
                    copy2(filenames[i-1], filenames[i])
                except:
                    pass
        oldprocs = []
        for index, src in utility.torrentconfig.Items():
            try:
                index = int(index)
                oldprocs.append((index, src.strip("\"")))
            except:
                pass
        oldprocs.sort()
        
        for index, src in oldprocs:
            self._addOldProc(src)
                    
        utility.queue.updateAndInvoke()

    def _dupFileInList(self, src, caller = ""):
        """
        Duplicate file error
        """
        errormsg = src + '\n\n' + _('This torrent (or one with the same hash value) already exists in the list')
        
        if caller not in ["web", "command"]:
            dlg = wx.MessageDialog(None, 
                                   errormsg, 
                                   _('Duplicate torrent'), 
                                   wx.OK|wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            
        return False, errormsg

    def _checkForDuplicateInList(self, infohash = None, src = None):
        """
        See if a torrent already in the list has the same infohash
        """
        for torrent in utility.torrents["all"]:
            if (src is not None and src == torrent.src) or \
               (infohash is not None and infohash == torrent.infohash):
                return True
        return False
